/**
 * 
 */
package core;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import util.Constants;
import util.NBGStrParser;
import NET.sourceforge.BplusJ.BplusJ.BplusTree;

/**
 * TODO replace indexing partitions with rectangle partitions TODO indexing
 * extensively with MySQL<br>
 * TODO build index from rid to partition. Given query, get rids then get
 * partition numbers directly.<br>
 * Parinet implementation<br>
 * Read NBG data set directly Road id -> partition partition -> b+tree having
 * timestamp=>oid
 * 
 * @author Xiaohui
 * @version 0.1 rid->partition mapping is changed to spatial proximity from
 *          modulo; partition changed to 2d array
 * @version 0.2 Multiple queries allowed
 */
public class ParinetV2 implements Constants {

	static int numPartitions = 4; // 4x4 grids
	String prefix = Constants.pathPrefix + "meta/";
	Rectangle2D.Double[][] partitions;
	RoadNetworkIndex rnidxer;
	Map<Long, Set<Rectangle2D>> ridToRect; //from rid to which rectangle

	/**
	 * Constructor
	 * 
	 * @throws Exception
	 */
	public ParinetV2() throws Exception {
		rnidxer = new RoadNetworkIndex();
		buildPartitions();
		ridToRect = new HashMap<Long, Set<Rectangle2D>>();
	}

	void buildPartitions() throws Exception {
		double stepx = (oldmaxx - oldminx) / numPartitions;
		double stepy = (oldmaxy - oldminy) / numPartitions;
		// separate the space and get right geometry
		partitions = new Rectangle2D.Double[numPartitions][numPartitions];
		for (int i = 0; i < numPartitions; i++) {
			double lowx = oldminx + stepx * i;
			double highx = oldminx + stepx * (i + 1);
			if (i == numPartitions - 1) {
				highx = oldmaxx;
			}
			for (int j = 0; j < numPartitions; j++) {
				double lowy = oldminy + stepy * j;
				double highy = oldminy + stepy * (j + 1);
				if (j == numPartitions - 1) {
					highy = oldmaxy;
				}
				partitions[i][j] = new Rectangle2D.Double(lowx, lowy, highx
						- lowx, highy - lowy);
			}
		}
	}

	private void printPartitions() {
		for (int i = 0; i < numPartitions; i++) {
			for (int j = 0; j < numPartitions; j++) {
				System.out.println(partitions[i][j]);
			}
		}
	}

	String getTreeNameById(int id) {
		return prefix + "tree" + id;
	}

	String getBlockNameById(int id) {
		return prefix + "block" + id;
	}

	/**
	 * Write input lines into several files according to the geometric relations
	 * akin to distribute data into respective regions <br>
	 * Generate sql statements to create tables, upload data and index
	 * 
	 * @param rawFileName
	 * @param outFilePrefix
	 */
	public void separateIntoFiles(String rawFileName, String outFilePrefix,
			String sqlOutFile) {
		System.out.println("Separating data into partitions...");
		BufferedReader in;
		BufferedWriter[][] bws = new BufferedWriter[numPartitions][numPartitions];
		try {
			BufferedWriter sqlOut = new BufferedWriter(new FileWriter(
					sqlOutFile));
			sqlOut.write("use traj;");
			sqlOut.newLine();

			// init bws, every rectangle corresponds to one writer
			for (int i = 0; i < numPartitions; i++) {
				for (int j = 0; j < numPartitions; j++) {
					String temp = outFilePrefix + i + j;
					bws[i][j] = new BufferedWriter(new FileWriter(temp));
					String table = temp.substring(temp.lastIndexOf("/") + 1);

					String sqlCreate = "DROP TABLE IF EXISTS "
							+ table
							+ ";\n"
							+ "CREATE TABLE  "
							+ table
							+ "(\n"
							+ "`state` varchar(15) DEFAULT NULL,\n"
							+ "`id` int(11) DEFAULT NULL,\n"
							+ "`seq_no` int(11) DEFAULT NULL,\n"
							+ "`class_id` int(11) DEFAULT NULL,\n"
							+ "`curr_time` int(11) DEFAULT NULL,\n"
							+ "`x_coord` decimal(13,7) DEFAULT NULL,\n"
							+ "`y_coord` decimal(13,7) DEFAULT NULL,\n"
							+ "`curr_speed` int(8) DEFAULT NULL,\n"
							+ "`roadid` int(11) unsigned DEFAULT NULL,\n"
							+ "`x_nextNode` int(10) unsigned DEFAULT NULL,\n"
							+ "`y_nextNode` int(10) unsigned DEFAULT NULL), KEY(curr_time)"
							+ ";\n";
					String sqlLoad = "LOAD DATA LOCAL INFILE '" + temp
							+ "' INTO TABLE " + table + ";";
					sqlOut.write(sqlCreate);
					sqlOut.write(sqlLoad);
					sqlOut.newLine();
					sqlOut.newLine();
				}
			}
			in = new BufferedReader(new FileReader(rawFileName));
			String str;
			NBGStrParser parser;
			int count = 1;
			while ((str = in.readLine()) != null) {
				// write into right file
				if ((count % 100000) == 0)
					System.out.println("Parinet separating progress: " + count);
				count++;

				parser = new NBGStrParser(str);
				Point2D.Double pt = new Point2D.Double(parser.getCurrPosx(),
						parser.getCurrPosy());
				Long rid = parser.getRoadid();
				
				// check which rectangle this point belongs to, and write
				for (int i = 0; i < numPartitions; i++) {
					for (int j = 0; j < numPartitions; j++) {
						if (partitions[i][j].contains(pt)) {
							bws[i][j].write(str);
							bws[i][j].newLine();
							//put the rectangle into mapping 
							if (ridToRect.get(rid)==null){
								Set<Rectangle2D> rectSet= new HashSet<Rectangle2D>();
								rectSet.add(partitions[i][j]);
								ridToRect.put(rid, rectSet);
							}else{
								Set<Rectangle2D> rectSet = ridToRect.get(rid);
								rectSet.add(partitions[i][j]);
							}
							
						}
					}

				}
			}
			in.close();
			for (int i = 0; i < bws.length; i++) {
				for (int j = 0; j < numPartitions; j++) {
					bws[i][j].close();
				}
			}
			sqlOut.close();
			System.out
					.println("Data separation and sql scripts generation Done!");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Find partitions by looking up mapping table (rid->partitions)
	 * 
	 * @param node1
	 *            of the road edge
	 * @param node2
	 *            of the road edge
	 * @return set of id of the partition intersecting with the road
	 */
	public void fillIntersectedPartids(long roadId, 
			Set<Rectangle2D> qualifiedPartitions) throws Exception {
		//TODO consult mapping for qualified Partitions
	}

	// get partitions by intersecting query window with road network to
	// get set of road ids
	// followed by finding the partitions that contain the road ids
	/**
	 * @param queryType
	 *            , region
	 * @return a list of road id's that are intersecting with the query region
	 * @throws Exception
	 */
	public Set<Rectangle2D> doSpatialFiltering(String queryType, String treeFile,
			QueryParser qparser) throws Exception {
		long start = System.currentTimeMillis();
		Set<Long> roadIds = rnidxer
				.RTreeQuery4Rid(qparser, treeFile, queryType);
		// System.out.println("road ids size: " + roadIds.size());
		BplusTree tree = BplusTree.ReadOnly(Constants.networkBTreeFileByEid,
				Constants.networkBlockFileByEid);
		Set<Rectangle2D> partitionNumbers = new HashSet<Rectangle2D>();
		for (long rid : roadIds) {
			// System.out.println("Now processing rid :"+rid);
			fillIntersectedPartids(rid, partitionNumbers);
			// System.out.println("Now partition numbers size: "+partitionNumbers.size());
		}
		long end = System.currentTimeMillis();
		System.out.println("num partitions to examine: "
				+ partitionNumbers.size());
		System.out.println("Spatial Fileter(ms): " + (end - start));
		return partitionNumbers;
	}

	public List<QueryParser> readQueryFile(String queryFile) {
		return QueryFileReader.getParsers(queryFile);
	}

	/**
	 * TODO do temporal filtering in these partitions with last two numbers in
	 * // the query file
	 */
	public Set<Integer> DBTemporalFilter(List<QueryParser> queryParsers,
			Set<Partition> partitionSet) throws Exception {
		Set<Integer> objidSet = new HashSet<Integer>();
		long temporalStart = System.currentTimeMillis();
		for (QueryParser qp : queryParsers) {
			double startTime = qp.getStartTime();
			double endTime = qp.getEndTime();

			// System.out.println("start time" + startTime);
			// System.out.println("end time: " + endTime);
			for (Partition pn : partitionSet) {

				BplusTree tree = BplusTree.ReadOnly(pn.getTreeFileName(), pn
						.getBlockFileName());
				for (int i = (int) startTime; i < (int) endTime + 1; i++) {
					if (tree.ContainsKey("" + i)) {
						String val = tree.get("" + i);
						StringTokenizer st = new StringTokenizer(val, ",");
						while (st.hasMoreElements()) {
							objidSet.add(Integer.parseInt(st.nextToken()));
						}

					}
				}
			}
		}
		long temporalEnd = System.currentTimeMillis();
		System.err.println("Temporal Filter(ms): "
				+ (temporalEnd - temporalStart));
		return objidSet;
	}

	/**
	 * do temporal filtering in these partitions with last two numbers in // the
	 * query file
	 */
	public Set<Integer> doTemporalFilter(QueryParser qparser,
			Set<Partition> partitionSet) throws Exception {
		Set<Integer> objidSet = new HashSet<Integer>();

		double startTime = qparser.getStartTime();
		double endTime = qparser.getEndTime();
		long start = System.currentTimeMillis();
		// System.out.println("start time" + startTime);
		// System.out.println("end time: " + endTime);
		for (Partition pn : partitionSet) {
			BplusTree tree = BplusTree.ReadOnly(pn.getTreeFileName(), pn
					.getBlockFileName());
			for (int i = (int) startTime; i < (int) endTime + 1; i++) {
				if (tree.ContainsKey("" + i)) {
					String val = tree.get("" + i);
					StringTokenizer st = new StringTokenizer(val, ",");
					while (st.hasMoreElements()) {
						objidSet.add(Integer.parseInt(st.nextToken()));
					}
				}
			}
			tree.Shutdown();
			tree = null;
		}
		long end = System.currentTimeMillis();
		System.out.println("Temporal filtering(ms): " + (end - start));
		System.out.println("After temporal filtering, candidate size: "
				+ objidSet.size());
		System.out.println(objidSet);
		return objidSet;
	}

	public void executeSQLFile(String sqlFile) throws Exception {
		File file = new File(sqlFile);
		if (file.exists()) {
			Runtime.getRuntime().exec("mysql -u root -p bebestyou<" + sqlFile);
		} else {
			System.out.println("Running: " + " mysql -u root -p bebestyou<"
					+ sqlFile);
			System.out.println(sqlFile + " does not exist");
			System.exit(0);
		}
		System.out.println("Table creation and uploading DONE!");
	}

	// public static void main(String args[]) {
	// separateIntoFiles(args[0], args[1], args[2]);
	// }
}
